// Copyright (c) 2023 CHANGLEI. All rights reserved.

// ignore_for_file: avoid_redundant_argument_values

part of 'arith.dart';

/// Created by changlei on 2023/6/7.
///
/// 中缀表达式转换为后缀表达式（调度场算法）
///<br>
/// 1.输入队列弹出一个记号<br>
/// 2.如果记号为数字，添加到输出队列中<br>
/// 3.如果是一个操作符（+-*/）则比较它与输出堆栈中栈顶的操作符，如果优先级小于或等于栈顶的操作符，那么将栈顶的操作符弹出并加入输出队列（循环，直到上述条件不满足），最后将本次的操作符压入堆栈。<br>
/// 4.如果是一个左括号，压入堆栈<br>
/// 5.如果是一个右括号，从栈中不断的弹出操作符，并加入输出队列，知道栈顶的元素为左括号。弹出左括号，不加入输出队列。如果没有发现左括号，说明原来的表达式中括号不对称，有错误。<br>
/// 6.如果输入队列为空，而栈中尚有操作符时，如果栈顶的操作符为左括号，则说明原表达式有不匹配的括号。将栈中的操作符逐个弹出，加入输出队列。<br>
/// 7.完成<br>
/// <img src='https://bj-rwxipad.oss-cn-beijing.aliyuncs.com/410016230607dee6ea5e0df94b81bcedd4a818dd7148.png' width="400" height="400"/><br>
final class RPN {
  /// 中缀表达式转换为后缀表达式（调度场算法）
  const RPN(this._factors);

  static const _isTight = false;

  final Iterable<Factor> _factors;

  /// 转换
  Iterable<(int, Operable)> toRPN() {
    final inputStack = Queue<Factor>.of(_factors);
    final operatorStack = Queue<(int, Factor)>();
    final outputStack = Queue<(int, Factor)>();
    int index = 0;
    while (inputStack.isNotEmpty) {
      final current = inputStack.removeFirst();
      switch (current) {
        case Variables():
          outputStack.add((index, current));
        case Symbols():
          switch (current) {
            case Curve.left:
              operatorStack.add((index, current));
            case Curve.right:
              if (operatorStack.isEmpty) {
                throw ArithError.curveRight(_factors, index, current);
              }
              var last = operatorStack.removeLast();
              while (last.$2 != Curve.left) {
                outputStack.add(last);
                if (operatorStack.isNotEmpty) {
                  last = operatorStack.removeLast();
                } else {
                  break;
                }
              }
              if (last.$2 != Curve.left) {
                throw ArithError.curveLeft(_factors, index, current);
              }
            default:
              while (operatorStack.isNotEmpty && current <= operatorStack.last.$2) {
                final last = operatorStack.removeLast();
                if (current.priority == last.$2.priority && current is Operator1) {
                  operatorStack.add(last);
                  break;
                } else {
                  outputStack.add(last);
                }
              }
              operatorStack.add((index, current));
          }
      }
      index += current.toDescribe(_isTight).length;
    }
    while (operatorStack.isNotEmpty) {
      final last = operatorStack.removeLast();
      if (last.$2 is Curve) {
        throw ArithError.curveFactor(_factors, last.$1, last.$2);
      }
      outputStack.add(last);
    }
    return outputStack.cast();
  }

  /// 计算
  num call([Map<String, ValueGetter<num>>? variables]) {
    final rpnQueue = Queue.of(toRPN());
    final outputStack = Queue<Object>();
    while (rpnQueue.isNotEmpty) {
      final (index, current) = rpnQueue.removeFirst();
      switch (current) {
        case Operator1():
          if (outputStack.isEmpty) {
            throw ArithError.operator(_factors, index, current);
          }
          final factor = _value(outputStack.removeLast());
          if (factor == null) {
            throw ArithError.operator(_factors, index, current);
          }
          outputStack.add(current(factor));
        case Operator2():
          final factors = List.generate(2, (_) => outputStack.isEmpty ? null : _value(outputStack.removeLast()));
          if (factors.any((element) => element == null)) {
            throw ArithError.operator(_factors, index, current);
          }
          final [factor1, factor2] = factors.cast<num>().reversed.toList();
          outputStack.add(current(factor1, factor2));
        case Math():
          if (outputStack.isEmpty) {
            throw ArithError.function(_factors, index, current);
          }
          final factor = _values(outputStack.removeLast());
          if (factor == null) {
            throw ArithError.function(_factors, index, current);
          }
          try {
            outputStack.add(current(factor));
          } catch (e) {
            throw ArithError.function(_factors, index, current);
          }
        case Variables():
          outputStack.add(
            current.parse(variables, (before, argument, error) {
              final arguments = [...before, argument];
              final length = Variables(arguments).toDescribe(_isTight).length - argument.toDescribe(_isTight).length;
              final newIndex = index + length;
              switch (argument) {
                case Variable1():
                  throw ArithError.error(error, _factors, newIndex, argument);
                case Expression():
                  throw ArithError.stack(error, _factors, newIndex);
              }
            }),
          );
      }
    }
    return _value(outputStack.single) as num;
  }

  static num? _value(Object factor) {
    return switch (factor) {
      num() => factor,
      Iterable<Object>() => _value(factor.last),
      _ => null,
    };
  }

  static List<num>? _values(Object factor) {
    return switch (factor) {
      num() => [factor],
      Iterable<Object>() => factor.map(_value).cast<num>().toList(),
      _ => null,
    };
  }
}
